import { config } from "dotenv";
config();

import cluster from "cluster";
import os from "os";
import mainRouter from "./internal/mainRouter.js";
import express from "express";
import cors from "cors";
import chalk from "chalk";
import { apiRouter } from "./internal/api/apiRouter.js";
import webhooksRouter from "./external/webhooks/webhooksRouter.js";

import { initLogger } from "./errors/logger.js";
import { QueueManager } from "./queue/QueueManager.js";
import { AppEnv } from "@autumn/shared";
import { createSupabaseClient } from "./external/supabaseUtils.js";
import {
  createLogtail,
  createLogtailAll,
} from "./external/logtail/logtailUtils.js";
import { format } from "date-fns";
import { CacheManager } from "./external/caching/CacheManager.js";
import { initDrizzle } from "./db/initDrizzle.js";
import { createPosthogCli } from "./external/posthog/createPosthogCli.js";
import pg from "pg";
import http from "http";
import { generateId } from "./utils/genUtils.js";

const isDevelopment = process.env.NODE_ENV === "development";

if (!process.env.DATABASE_URL) {
  console.error(`DATABASE_URL is not set`);
  process.exit(1);
}

const init = async () => {
  try {
    const app = express();

    const logger = initLogger();
    const server = http.createServer(app);
    server.keepAliveTimeout = 120000; // 120 seconds
    server.headersTimeout = 120000; // 120 seconds should be >= keepAliveTimeout

    let pgClient;
    try {
      pgClient = new pg.Client(process.env.DATABASE_URL || "");
      await pgClient.connect();
    } catch (error) {
      console.error("Failed to connect to PostgreSQL:", error);
      if (!isDevelopment) {
        throw error;
      }
    }

    try {
      await QueueManager.getInstance(); // initialize the queue manager
    } catch (error) {
      console.error(
        "Failed to initialize QueueManager, continuing without queue functionality:",
        error,
      );
    }

    try {
      await CacheManager.getInstance();
    } catch (error) {
      console.error(
        "Failed to initialize CacheManager, continuing without cache functionality:",
        error,
      );
    }

    let supabaseClient;
    try {
      supabaseClient = createSupabaseClient();
    } catch (error) {
      console.error("Failed to initialize Supabase client:", error);
      if (!isDevelopment) {
        throw error;
      }
    }

    const logtailAll = createLogtailAll();

    let db;
    try {
      db = initDrizzle().db;
    } catch (error) {
      console.error("Failed to initialize Drizzle:", error);
      if (!isDevelopment) {
        throw error;
      }
    }

    let posthog;
    try {
      posthog = createPosthogCli();
    } catch (error) {
      console.error("Failed to initialize PostHog:", error);
      if (!isDevelopment) {
        posthog = {
          capture: () => {},
          identify: () => {},
        };
      } else {
        throw error;
      }
    }

    app.use((req: any, res: any, next: any) => {
      if (pgClient) req.pg = pgClient;
      if (supabaseClient) req.sb = supabaseClient;
      if (db) req.db = db;

      req.logger = logger;
      req.logtailAll = logtailAll;
      req.env = req.env = req.headers["app_env"] || AppEnv.Sandbox;

      req.logtailAll = logtailAll;
      if (posthog) req.posthog = posthog;

      req.id = req.headers["rndr-id"] || generateId("local_req");
      req.timestamp = Date.now();

      try {
        let headersClone = structuredClone(req.headers);
        headersClone.authorization = undefined;
        headersClone.Authorization = undefined;

        logtailAll.info(`${req.method} ${req.originalUrl}`, {
          url: req.originalUrl,
          method: req.method,
          headers: headersClone,
          body: req.body,
        });

        req.logtail = createLogtail();
        req.logger = req.logtail;
      } catch (error) {
        req.logtail = logtailAll; // fallback
        console.error(`Error creating req.logtail`);
        console.error(error);
      }

      res.on("finish", () => {
        try {
          req.logtail.flush();
        } catch (error) {
          console.error("Error flushing logs:", error);
        }
      });

      next();
    });

    app.use(
      cors({
        origin: ["http://localhost:3000", "http://localhost:3001"],
        methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
        allowedHeaders: [
          "Content-Type",
          "Authorization",
          "app_env",
          "x-publishable-key",
        ],
        credentials: true,
      }),
    );

    app.use("/webhooks", webhooksRouter);

    app.use((req: any, res, next) => {
      const method = req.method;
      const path = req.url;
      const methodToColor: any = {
        GET: chalk.green,
        POST: chalk.yellow,
        PUT: chalk.blue,
        DELETE: chalk.red,
        PATCH: chalk.magenta,
      };

      const methodColor: any = methodToColor[method] || chalk.gray;

      console.log(
        `${chalk.gray(format(new Date(), "dd MMM HH:mm:ss"))} ${methodColor(
          method,
        )} ${chalk.white(path)}`,
      );

      next();
    });

    app.use(express.json());

    app.use(mainRouter);
    app.use("/v1", apiRouter);

    const PORT = 8080;

    server.listen(PORT, () => {
      console.log(`Server running on port ${PORT}`);
    });
  } catch (error) {
    console.error("Failed to initialize server:", error);
    if (!isDevelopment) {
      process.exit(1);
    }
  }
};

// 在开发环境中，始终使用单进程模式
if (isDevelopment || true) {
  init();
} else {
  // 在开发环境中禁用集群模式
  /*
  let numCPUs = os.cpus().length;

  if (cluster.isPrimary) {
    console.log(`Master ${process.pid} is running`);
    console.log("Number of CPUs", numCPUs);
    let numWorkers = Math.min(numCPUs, 3);

    for (let i = 0; i < numWorkers; i++) {
      cluster.fork();
    }

    cluster.on("exit", (worker, code, signal) => {
      try {
        let logtail = createLogtail();
        logtail.error(`WORKER DIED: ${worker.process.pid}`);
        logtail.flush();
      } catch (error) {
        console.log("Error sending log to logtail", error);
      }
      // LOG in Render
      cluster.fork();
    });
  } else {
    init();
  }
  */
}
